package com.hup.minicontroller.client.service;

import android.util.Log;

import com.hup.minicontroller.client.ClientApplication;
import com.hup.minicontroller.client.model.AppConfig;
import com.hup.minicontroller.client.model.AppState;
import com.hup.minicontroller.client.model.LanBroadcastServer;
import com.hup.minicontroller.client.mvp.IMainView;
import com.hup.minicontroller.common.constant.BroadcastConstant;
import com.hup.minicontroller.common.framework.SubThreadHandler;
import com.hup.minicontroller.common.util.BroadcastUtil;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * 局域网UPD广播接收端;
 * 说明:server.LanBroadcastSenderService
 *
 * @author hugan
 * @date 2021/5/10
 */
public class LanBroadcastReceiverService {
    private static final String TAG = LanBroadcastReceiverService.class.getSimpleName();
    private static final long SERVER_EXPIRE_TIME = 10000;

    /**
     * 接收广播的独立线程;接收广播时是阻塞线程的,所以不能使用公用的Handler
     */
    private final SubThreadHandler handler;
    private final TreeSet<LanBroadcastServer> lanBroadcastServers;
    private Receiver receiver;

    private boolean isStart;
    /**
     * 上次清理过期搜索结果的时间
     */
    private long lastClearExpireServer;

    LanBroadcastReceiverService() {
        handler = new SubThreadHandler("LanBroadcastReceiver");
        lanBroadcastServers = new TreeSet<>();
        lastClearExpireServer = System.currentTimeMillis();
        try {
            receiver = new Receiver();
        } catch (Exception e) {
            Log.e(TAG, "LanBroadcastReceiverService: UDP广播初始化异常", e);
        }
    }

    public void switchService(boolean isStart) {
        Log.i(TAG, "switchService: " + isStart);
        if (receiver == null || this.isStart == isStart) return;
        this.isStart = isStart;
        if (isStart) {
            Log.i(TAG, "run: 开始接收广播");
            handler.post(receiver);
        } else {
            lanBroadcastServers.clear();
        }
    }

    public void doTerminate() {
        if (receiver != null) {
            receiver.doTerminate();
            lanBroadcastServers.clear();
            receiver = null;
        }
    }

    private void onReceived(String serverName, String hostAddress) {
        Log.d(TAG, "onReceived=[" + serverName + "], address=" + hostAddress);
        LanBroadcastServer newServer = new LanBroadcastServer(serverName, hostAddress, System.currentTimeMillis());
        lanBroadcastServers.remove(newServer);//移除旧item,注意此处重写过equal
        lanBroadcastServers.add(newServer);//TreeSet.add时,按照address排序,避免界面上的item跳来跳去
        ClientApplication.executeIfExist(IMainView.class, i -> i.onScannedBroadcastServer(lanBroadcastServers));
    }

    private void clearExpireService() {
        long cur = System.currentTimeMillis();
        if (cur - lastClearExpireServer < SERVER_EXPIRE_TIME) return;
        Log.d(TAG, "clearExpireService: ");
        Iterator<LanBroadcastServer> it = lanBroadcastServers.iterator();
        boolean changed = false;
        while (it.hasNext()) {
            LanBroadcastServer server = it.next();
            if (cur - server.getReceiveTime() > SERVER_EXPIRE_TIME) {
                it.remove();
                changed = true;
            }
        }
        if (changed) ClientApplication.executeIfExist(IMainView.class, i -> i.onScannedBroadcastServer(lanBroadcastServers));
        lastClearExpireServer = cur;
    }

    /**
     * <pre>
     * UDP广播接收端,正常代码流程:
     * .初始化MulticastSocket:端口号,接收超时
     * .初始化DatagramPacket,用于接收数据
     * .MulticastSocket加入广播组,注意这是加入一次就可以了
     * .循环开始接受数据
     *
     * 安卓joinGroup问题:
     * .无网时(没连wifi,也没开移动网络),joinGroup会异常
     * .joinGroup后,断网重连,会长时间都timeout,但后续可能会接收成功
     * .上述两个问题,只在安卓端出现,电脑版没有此问题,所以本类采用[每次接收数据时都重新joinGroup]的方案,避免网络切换时出现长时间都没在"接收状态"的情况
     */
    private class Receiver implements Runnable {
        private final AppState appState;

        private final InetAddress broadcastAddress;
        private MulticastSocket multicastSocketReceiver;
        private final byte[] buff;
        private final DatagramPacket packet;

        public Receiver() throws IOException {
            appState = ClientApplication.getBean(MainService.class).getAppState();
            AppConfig appConfig = appState.getAppConfig();
            broadcastAddress = InetAddress.getByName(appConfig.getBroadcastIp());
            multicastSocketReceiver = new MulticastSocket(appConfig.getLanBroadcastPort());
            multicastSocketReceiver.setSoTimeout(BroadcastConstant.CLIENT_RECEIVE_TIMEOUT);
            buff = new byte[1024];
            packet = new DatagramPacket(buff, buff.length);
        }

        @Override
        public void run() {
            if (!isStart || appState.getConnectedType() != null) {
                Log.i(TAG, "run: 停止接收广播");
                isStart = false;
                return;
            }
            clearExpireService();
            boolean joinedGroup = false;
            try {
                multicastSocketReceiver.joinGroup(broadcastAddress);
                joinedGroup = true;
            } catch (IOException e) {
                String message = e.getMessage();
                if (message != null && message.contains("already")) {
                    /*
                    already加入异常:仅发生在部分设备,
                    虽然是already加入,但下次receive是一定报超时的,所以直接leaveGroup,避开没必要的等待时间
                     */
                    Log.d(TAG, "重复加入广播组");
                    BroadcastUtil.leaveGroup(multicastSocketReceiver, broadcastAddress);
                } else {
                    //没联网
                    Log.d(TAG, "加入广播组异常:" + e);
                }
            }
            if (joinedGroup) {
                try {
                    packet.setData(buff);//每次receive前都要重置packet
                    multicastSocketReceiver.receive(packet);
                    int readLength = packet.getLength();
                    if (readLength == 0 || readLength < BroadcastConstant.MAX_MSG_LENGTH) {
                        byte[] data = packet.getData();
                        String msg = new String(data, 0, readLength);
                        if (msg.startsWith(BroadcastConstant.MSG_HEAD)) {
                            msg = msg.substring(BroadcastConstant.MSG_HEAD.length());
                            String hostAddress = packet.getAddress().getHostAddress();
                            onReceived(msg, hostAddress);
                        }
                    }
                } catch (IOException e) {
                    /*
                    一般是SocketTimeoutException,原因:
                    .服务端无上线
                    .已联网情况下,断网了
                     */
                    Log.d(TAG, "广播接收错误: " + e);
                } finally {
                    //不管接收是否成功,都leaveGroup
                    BroadcastUtil.leaveGroup(multicastSocketReceiver, broadcastAddress);
                }
            }
            if (!isStart) {
                Log.i(TAG, "run: 停止接收广播");
            } else if (!joinedGroup) {
                //joinGroup错误(没连wifi,热点等),等待n秒后重新run
                handler.postDelayed(this, 1000);
            } else {
                /*
                .消息接收成功:马上继续接收(可能有多个服务端的消息)
                .消息接收失败:也马上继续接收,因为一般是超时异常,已经等得了n秒了
                 */
                handler.post(this);
            }
        }

        public void doTerminate() {
            Log.i(TAG, "doTerminate: ");
            isStart = false;
            multicastSocketReceiver.close();
        }


    }

}
